local abr = minetest.get_mapgen_setting('active_block_range')

local goronner = {}
--goronner.spawn_rate = 0.5		-- less is more

local min=math.min
local max=math.max

local spawn_rate = 1 - max(min(minetest.settings:get('goronner_spawn_chance') or 0.2,1),0)
local spawn_reduction = minetest.settings:get('goronner_spawn_reduction') or 0.5

local players= {}
local globalstepTime= 1.0
local globalstepCountdown= globalstepTime
local immuneResponseVelocity= 0.015
local immuneResponseMax= 2.0
local immuneResponseDecay= 0.011
local viralLoadPerAttack= 0.3
local viralLoadDecay= 0.01
local viralLoadMax= 5.0

local goronner_chatlog= false
local function glog(fmt, ...)
    local f= string.format("[goronner] " .. fmt, ...)
    minetest.log(f)
    if goronner_chatlog then minetest.chat_send_all(f) end
end

local function chatf(fmt, ...)
    local f= string.format("[goronner] " .. fmt, ...)
    minetest.log(f)
    minetest.chat_send_all(f)
end

minetest.register_chatcommand("goronner_chatlog", {
    params= "<on|off>",
    description= "send goronner log messages to chat",
    privs= { debug= true },
    func= function(name, param)
        if param ~= "" then
            if param:lower() == "on" then goronner_chatlog= true
            elseif param:lower() == "off" then goronner_chatlog= false
            end
        end
        minetest.chat_send_player(name, string.format("goronner chatlog is %s", goronner_chatlog and 'on' or 'off'))
    end
})




minetest.register_on_joinplayer(function(player)
    local playerName = player:get_player_name()
    local huddef= {
        hud_elem_type= "image",
        -- position= { x=1.0, y=1.0 },

        -- links neben standard-hotbar
        --position = {x=0.5,y=1},
        --alignment = {x=0,y=1},
        --offset = {x=-261, y=-65},
        
        -- ganz links unten
        --position = {x=0, y=1},
        --alignment = {x=0, y=1},
        --offset = {x=40, y=-64},
        
        -- links von health & sprint bar
        position = {x=0.5, y=1},
        alignment = {x=0, y=1},
        offset = {x=-300, y=-128},
        
        -- 
        -- position = {x=1.0, y=1},
        -- alignment = {x=1, y=1},
        -- offset = {x=-64, y=-64},
        
        name= "goronner",
        scale= { x=2, y=2 },
        text= "goronner-hud-inf-01.png",
        z_index = 0,
    }
    players[playerName] = {
        viralLoad= 0.0,
        immuneResponse= 0.0,
        hudViralLoad= player:hud_add(huddef),
        hudImmunity= player:hud_add(huddef)
    }
end)

minetest.register_on_leaveplayer(function(player)
    local playerName = player:get_player_name()
    players[playerName] = nil
end)

minetest.register_globalstep(function(dtime)
    globalstepCountdown= globalstepCountdown - dtime
    if globalstepCountdown>0 then return end
    globalstepCountdown= globalstepCountdown + globalstepTime
    
    --Loop through all connected players
    for playerName, playerInfo in pairs(players) do
        local player = minetest.get_player_by_name(playerName)
        if player ~= nil then
            if playerInfo.viralLoad > viralLoadDecay then
                local playerPos = player:getpos()
                --local playerNode = minetest.get_node({x=playerPos["x"], y=playerPos["y"]-1, z=playerPos["z"]})
                --if playerNode["name"] ~= "air" then
                    local p= {
                        amount = 5,
                        time = 0.01,
                        minpos = vector.add(playerPos, {x=-.25,y=2.0,z=-.25}),
                        maxpos = vector.add(playerPos, {x= .25,y=2.2,z= .25}),
                        minvel = {x=-1, y= 1, z=-1},
                        maxvel = {x= 1, y= 2, z= 1},
                        minacc = {x=-3, y= 1, z=-3},
                        maxacc = {x= 3, y= 1, z= 3},
                        minexptime= .5,
                        maxexptime= .75,
                        minsize = 1,
                        maxsize = 1,
                        collisiondetection = true,
                        vertical = false,
                        texture = "goronner-particle2.png",
                        glow= 20
                    }
                    minetest.add_particlespawner(p)
                
                    if math.random() < 0.05 then --math.max(0.01, math.min(0.06, playerInfo["viralLoad"]*0.05)) then
                        local spawnpos= vector.add(playerPos, {x=0,y=2,z=0})
                        if minetest.get_node(spawnpos).name=="air" then
                            minetest.add_entity(spawnpos, "goronner:goronner", minetest.serialize({memory={incubator=playerName}}))
                        else
                            glog("not spawning (not in air)")
                        end
                        --o:set_yaw(player:get_yaw())
                    end
                
                playerInfo["viralLoad"]= playerInfo["viralLoad"]-viralLoadDecay
                playerInfo["immuneResponse"]= playerInfo["immuneResponse"]+immuneResponseVelocity
                if playerInfo["immuneResponse"] > immuneResponseMax then playerInfo["immuneResponse"]= immuneResponseMax end

                glog("%s viral load: %.1f, immune response: %.1f", playerName, playerInfo.viralLoad, playerInfo.immuneResponse)

            elseif playerInfo["immuneResponse"]>immuneResponseDecay then
                playerInfo["immuneResponse"]= playerInfo["immuneResponse"]-immuneResponseDecay
                glog("%s viral load: %.1f, immune response: %.1f", playerName, playerInfo.viralLoad, playerInfo.immuneResponse)
            end
            
            local pmeta= player:get_meta()
            for key,value in pairs(playerInfo) do
                pmeta:set_float("goronner:" .. key, value)
            end
            
            -- chatf("meta test: %s", player:get_meta():get("goronner:viralLoad"))
            
            -- update HUD
            local viralLoadTex= math.min( math.floor(playerInfo["viralLoad"]*4), 4 ) + 1
            player:hud_change(playerInfo["hudViralLoad"], "text", string.format("goronner-hud-inf-%02d.png", viralLoadTex))
            
            local viralImmunityTex= math.min( math.floor(playerInfo["immuneResponse"]*8), 8 ) + 1
            player:hud_change(playerInfo["hudImmunity"], "text", string.format("goronner-hud-imm-%02d.png", viralImmunityTex))
        end
    end
end)


local function node_dps_dmg(self)
    local pos = self.object:get_pos()
    local box = self.object:get_properties().collisionbox
    local pos1 = {x = pos.x + box[1], y = pos.y + box[2], z = pos.z + box[3]}
    local pos2 = {x = pos.x + box[4], y = pos.y + box[5], z = pos.z + box[6]}
    local nodes_overlap = mobkit.get_nodes_in_area(pos1, pos2)
    local total_damage = 0

    for node_def, _ in pairs(nodes_overlap) do
        local dps = node_def.damage_per_second
        if dps then
            total_damage = math.max(total_damage, dps)
        end
    end

    if total_damage ~= 0 then
        mobkit.hurt(self, total_damage)
    end
end


function mobkit.lq_jumpattack(self,height,target)
    local phase=1		
    local timer=0.5
    local tgtbox = target:get_properties().collisionbox
    local func= function(self)
        if not mobkit.is_alive(target) then return true end

        local pname= target:get_player_name()
        if pname ~= "" then
            if (players[pname] ~= nil and players[pname]["immuneResponse"] > 0.75)
                or mobkit.recall(self, "tamer") == pname 
                or mobkit.recall(self, "following") ~= nil then 
                    return true 
            end
        end

        if self.isonground then
            if phase==1 then	-- collision bug workaround
                local vel = self.object:get_velocity()
                vel.y = -mobkit.gravity*math.sqrt(height*2/-mobkit.gravity)
                self.object:set_velocity(vel)
                mobkit.make_sound(self, 'charge')
                phase=2
            else
                mobkit.lq_idle(self,0.3)
                return true
            end
        elseif phase==2 then
            local dir = minetest.yaw_to_dir(self.object:get_yaw())
            local vy = self.object:get_velocity().y
            dir=vector.multiply(dir,6)
            dir.y=vy
            self.object:set_velocity(dir)
            phase=3
        elseif phase==3 then	-- in air
            local tgtpos = target:get_pos()
            local pos = self.object:get_pos()
            -- calculate attack spot
            local yaw = self.object:get_yaw()
            local dir = minetest.yaw_to_dir(yaw)
            local apos = mobkit.pos_translate2d(pos,yaw,self.attack.range)

            if mobkit.is_pos_in_box(apos,tgtpos,tgtbox) then	--bite
                local pname= target:get_player_name()
                if pname ~= "" then
                    glog("infecting %s", pname)
                    players[pname]["viralLoad"]= players[pname]["viralLoad"] + viralLoadPerAttack
                    if players[pname]["viralLoad"] > viralLoadMax then players[pname]["viralLoad"]= viralLoadMax end
                end
                target:punch(self.object,1,self.attack)
                    -- bounce off
                local vy = self.object:get_velocity().y
                self.object:set_velocity({x=dir.x*-3,y=vy,z=dir.z*-3})	
                    -- play attack sound if defined
                mobkit.make_sound(self,'attack')
                phase=4
            end
        end
    end
    mobkit.queue_low(self,func)
end

-- find target to attack or nil
local function find_target(self)
    local target= nil
    for _,obj in ipairs(self.nearby_objects) do
        if obj:is_player() and mobkit.is_alive(obj) and obj:get_player_name() ~= mobkit.recall(self, 'tamer') then
            if target==nil or
               vector.distance(obj:get_pos(), self.object:get_pos()) < 
               vector.distance(target:get_pos(), self.object:get_pos()) then
                target= obj
            end
        end
    end
    return target
end

local function goronner_die(self)
    local pos= self.object:get_pos()
    mobkit.clear_queue_high(self)									-- cease all activity
    mobkit.make_sound(self, "die")
    local rewards= { 
        "farming:bottle_ethanol", "farming:bottle_ethanol", "farming:bottle_ethanol", "farming:bottle_ethanol", 
        "default:water_source", "default:water_source", 
        "default:lava_source", 
    }
    local reward= rewards[math.random(#rewards)]
    glog("spawning %s", reward)
    minetest.add_item(pos, reward)
    if math.random() < 0.05 then
        -- lol
        local o
        o= minetest.add_entity(vector.add(pos, {x=-1,y=0,z=0}), "goronner:goronner")
        o:set_yaw(self.object:get_yaw())
        o= minetest.add_entity(vector.add(pos, {x= 1,y=0,z=0}), "goronner:goronner")
        o:set_yaw(self.object:get_yaw())
    end
    local p= {
        amount = 40,
        time = 0.01,
        minpos = vector.add(pos, {x=-.4,y=.1,z=-.4}),
        maxpos = vector.add(pos, {x= .4,y=.4,z= .4}),
        minvel = {x=-1, y= 4, z=-1},
        maxvel = {x= 1, y= 6, z= 1},
        minacc = {x=-3, y=-20, z=-3},
        maxacc = {x= 3, y=-20, z= 3},
        minexptime= .5,
        maxexptime= .75,
        minsize = 1,
        maxsize = 1,
        collisiondetection = true,
        vertical = false,
        texture = "goronner-particle.png",
        glow= 20
    }
    minetest.add_particlespawner(p)
    p.texture= "goronner-particle2.png"
    p.amount= 10
    p.minpos= vector.add(self.object:get_pos(), {x=-.75,y=.1,z=-.75})
    p.maxpos= vector.add(self.object:get_pos(), {x= .75,y=.4,z= .75})
    minetest.add_particlespawner(p)
    self.logic= nil
    mobkit.clear_queue_low(self)
    self.object:remove()
end

local function predator_brain(self)
    -- vitals should be checked every step
    if mobkit.timer(self,1) then node_dps_dmg(self) end
    mobkit.vitals(self)

    if self.hp <= 0 then
        goronner_die(self)
        return
    end
    
    if mobkit.timer(self,1) then 			-- decision making needn't happen every engine step
        local prty = mobkit.get_queue_priority(self)
        
        if prty < 20 and self.isinliquid then
            mobkit.hq_liquid_recovery(self,20)
            return
        end
        
        local pos=self.object:get_pos()
        
        if prty < 9 and mobkit.recall(self, 'following')==nil then
            local plyr = find_target(self)					
            if plyr and vector.distance(pos,plyr:get_pos()) < 10 
                    and players[plyr:get_player_name()]["immuneResponse"] < 1.0 then
                mobkit.hq_warn(self,9,plyr)								-- try to repel them
                                                                        -- hq_warn will trigger subsequent bhaviors if needed
            end
        end
        
        -- fool around
        if mobkit.is_queue_empty_high(self) then
            if prty < 9 then
                if math.random()<0.05 then
                    mobkit.lq_turn2pos(self, vector.add(pos, {x=math.random()-0.5, y=math.random()-0.5, z=math.random()-0.5}) )
                end
            end
        end
    end
    
    if mobkit.get_queue_priority(self) < 9 then
        if mobkit.timer(self, 0.5) then
            if math.random() < 0.125 then
                mobkit.animate(self, 'stand')
                mobkit.animate(self, 'wobble0')
                mobkit.make_sound(self, 'idle')
            elseif math.random() < 0.075 then
                mobkit.animate(self, 'stand')
                mobkit.animate(self, 'wink0')
                mobkit.make_sound(self, 'wink')
            end
        end

        mobkit.go_forward_horizontal(self, self._walkspeed)
        self._walkspeed= self._walkspeed*0.99
        if self._walkspeed<0.1 then
            self._walkspeed= math.random()*5.0 + 1.0
            mobkit.make_sound(self, 'roam')
            mobkit.lq_dumbjump(self, 1.5)
        end
    end
end

-- spawning is too specific to be included in the api, this is an example.
-- a modder will want to refer to specific names according to games/mods they're using 
-- in order for mobs not to spawn on treetops, certain biomes etc.

local function spawnstep(dtime)
    for _,plyr in ipairs(minetest.get_connected_players()) do
        if math.random()<dtime*0.2 then	-- each player gets a spawn chance every 5s on average
            local vel = plyr:get_player_velocity()
            local spd = vector.length(vel)
            local chance = spawn_rate * 1/(spd*0.75+1)  -- chance is quadrupled for speed=4

            local yaw
            if spd > 1 then
                -- spawn in the front arc
                yaw = plyr:get_look_horizontal() + math.random()*0.35 - 0.75
            else
                -- random yaw
                yaw = math.random()*math.pi*2 - math.pi
            end
            local pos = plyr:get_pos()
            local dir = vector.multiply(minetest.yaw_to_dir(yaw),abr*16)
            local pos2 = vector.add(pos,dir)
            pos2.y=pos2.y-5
            local height, liquidflag = mobkit.get_terrain_height(pos2,32)
    
            if height and height >= 0 and not liquidflag -- and math.abs(height-pos2.y) <= 30 testin
            and mobkit.nodeatpos({x=pos2.x,y=height-0.01,z=pos2.z}).is_ground_content then

                local objs = minetest.get_objects_inside_radius(pos,abr*16+5)
                local wcnt=0
                local dcnt=0
                for _,obj in ipairs(objs) do				-- count mobs in abrange
                    if not obj:is_player() then
                        local luaent = obj:get_luaentity()
                        if luaent and luaent.name:find('goronner:') then
                            chance=chance + (1-chance)*spawn_reduction	-- chance reduced for every mob in range
                            if luaent.name == 'goronner:goronner' then wcnt=wcnt+1
                            elseif luaent.name=='goronner:deer' then dcnt=dcnt+1 end
                        end
                    end
                end
--minetest.chat_send_all('chance '.. chance)
                if chance < math.random() then

                    -- if no wolves and at least one deer spawn goronner, else deer
--					local mobname = (wcnt==0 and dcnt > 0) and 'goronner:goronner' or 'goronner:deer'
                    local mobname = 'goronner:goronner'  -- XXXXX dcnt>wcnt+1 and 'goronner:goronner' or 'goronner:deer'

                    pos2.y = height+0.5
                    objs = minetest.get_objects_inside_radius(pos2,abr*16-2)
                    for _,obj in ipairs(objs) do				-- do not spawn if another player around
                        if obj:is_player() then return end
                    end
--minetest.chat_send_all('spawnin '.. mobname ..' #deer:' .. dcnt)
                    minetest.add_entity(pos2,mobname)			-- ok spawn it already damnit
                end
            end
        end
    end
end


minetest.register_globalstep(spawnstep)


-- mobkit memory:
--  incubator: name of player who spawned it
minetest.register_entity("goronner:goronner", {
                                            -- common props
    physical = true,
    stepheight = 0.1,				--EVIL!
    collide_with_objects = true,
    collisionbox =  {-0.4, 0.0, -0.4, 0.4, 1.0, 0.4},  -- Default
        --{-0.3, -0.01, -0.3, 0.3, 0.3, 0.3},
    visual = "mesh",
    mesh = "goronner.md2",
    textures = { "goronner.png" },
    visual_size = {x = 0.4, y = 0.4},
    static_save = true,
    makes_footstep_sound = false,
    on_step = mobkit.stepfunc,	-- required
    on_activate = function(self, staticdata, dtime_s)
        mobkit.actfunc(self, staticdata, dtime_s)    -- required
        mobkit.lq_turn2pos(self, vector.add(self.object:get_pos(), {x=math.random()-0.5, y=math.random()-0.5, z=math.random()-0.5}) )
        self:_update_nametag()
        
        -- change texture:
        --self.object:set_properties( { textures= { "kit_wolf.png" } } )
    end,
    get_staticdata = mobkit.statfunc,
                                            -- api props
    springiness= 0,
    buoyancy = 0.75,					-- portion of hitbox submerged
    max_speed = 5,
    jump_height = 2.5,
    view_range = 24,
    lung_capacity = 0.5, 		-- seconds
    max_hp = 12,
    timeout= 600,
    attack= { range= 1.0, damage_groups= { fleshy= 2 } },
    sounds = {
        charge= { 
            { name='goronner-attack0', gain=0.3, fade=0.0, pitch={ 0.85, 1.1 } }, 
            { name='goronner-attack1', gain=0.3, fade=0.0, pitch={ 0.85, 1.1 } }, 
            { name='goronner-attack2', gain=0.3, fade=0.0, pitch={ 0.85, 1.1 } }, 
            { name='goronner-attack3', gain=0.3, fade=0.0, pitch={ 0.85, 1.1 } }, 
        },
        roam= { 
            { name='goronner-roam1', gain=0.7, fade=0.0, pitch={ 0.85, 1.1 } }, 
            { name='goronner-roam2', gain=0.7, fade=0.0, pitch={ 0.85, 1.1 } }, 
            { name='goronner-roam3', gain=0.7, fade=0.0, pitch={ 0.85, 1.1 } }, 
            { name='goronner-roam4', gain=0.7, fade=0.0, pitch={ 0.85, 1.1 } }, 
        },
        idle= { 
            { name='goronner-idle1', gain=0.6, fade=0.0, pitch={ 0.8, 1.1 } }, 
            { name='goronner-idle2', gain=0.6, fade=0.0, pitch={ 0.8, 1.1 } }, 
            { name='goronner-idle3', gain=0.6, fade=0.0, pitch={ 0.8, 1.1 } }, 
            { name='goronner-idle4', gain=0.6, fade=0.0, pitch={ 0.8, 1.1 } }, 
        },
        die= { 
            { name='goronner-die1', gain=0.4, fade=0.0, pitch={ 0.8, 1.1 } }, 
        },
        wink= { 
            { name='goronner-wink1', gain=0.5, fade=0.0, pitch={ 0.7, 1.3 } }, 
        },
        hurt = {
            { name='goronner-hurt1', gain=0.6, fade=0.0, pitch={ 0.6, 1.3 } }, 
            { name='goronner-hurt2', gain=0.6, fade=0.0, pitch={ 0.6, 1.3 } }, 
        },
        -- warn = { 'goronner-attack2' },
    },
    animation = {
        wobble0= { 
            { range={x=3*0,y=3*3}, speed=8, loop=false },
            { range={x=3*3,y=3*6}, speed=10,loop=false },
            { range={x=3*2,y=3*4}, speed=12,loop=false },
            { range={x=3*5,y=3*8}, speed=11,loop=false },
        },
        wink0= {
            { range= { x=3*8, y=3*(8+25) }, speed=60, loop=false },
        },
    },

    brainfunc = predator_brain,
    
    on_punch= function(self, puncher, time_from_last_punch, tool_capabilities, dir)
        if mobkit.is_alive(self) then
            local punchitem= puncher:get_wielded_item()
            if puncher:is_player() and punchitem:get_name():match('ethanol') then
                punchitem:set_count(punchitem:get_count()-1)
                puncher:set_wielded_item(punchitem)
                self:_tame(puncher)
                return
            end
            local hvel = vector.multiply(vector.normalize({x=dir.x,y=0,z=dir.z}),4)
            self.object:set_velocity({x=hvel.x,y=2,z=hvel.z})
            
            mobkit.hurt(self,tool_capabilities.damage_groups.fleshy or 1)
            mobkit.make_sound(self, 'hurt')

            if type(puncher)=='userdata' and puncher:is_player() then	-- if hit by a player
                mobkit.clear_queue_high(self)							-- abandon whatever they've been doing
                mobkit.hq_hunt(self, 10, puncher)						-- get revenge
            end
        end
    end,
    
    on_rightclick = function(self, clicker)
        if not clicker:is_player() then return end
        local playerName= clicker:get_player_name()
        if playerName ~= mobkit.recall(self, "tamer") then return end

        if mobkit.recall(self, "following") ~= playerName then
            mobkit.remember(self, "following", playerName)
            mobkit.clear_queue_high(self)
            mobkit.hq_hunt(self, 10, clicker)      -- xxx hack. will follow the player, but not attack while tamed.
            minetest.chat_send_player(playerName, "following!")
        else
            mobkit.remember(self, "following", nil)
            mobkit.clear_queue_high(self)
            mobkit.lq_idle(self, 0.3)
            minetest.chat_send_player(playerName, "roaming.")
        end
    end,
    
    glow= 10.0,
    
    _walkspeed= 1.0,
    
    _tame= function(self, tamer)
        if not tamer:is_player() then return end
        local playerName= tamer:get_player_name()
        minetest.chat_send_player(playerName, "tamed!")
        mobkit.remember(self, "tamer", playerName)
        self:_update_nametag()
    end,
    
    _update_nametag= function(self)
        local tamer= mobkit.recall(self, "tamer")
        if tamer~=nil then
            self.object:set_nametag_attributes({text= string.format("%s's tamed goronner", tamer)})
        else
            local incubator= mobkit.recall(self, "incubator")
            if incubator~=nil then
                self.object:set_nametag_attributes({text= string.format("%s's goronner", incubator)})
            end
        end
    end,
})


local function guess_health(self)
    if self.hp~=nil then
        return self.hp
    elseif self.health~=nil then
        return self.health
    else
        return self.object:get_hp()
    end
end

minetest.register_on_mods_loaded(function()
    for k, v in pairs(minetest.registered_entities) do
        -- chatf("%s = %s", k, v)
        if string.split(k, ':')[1] ~= '__builtin' then
            --for kk, vv in pairs(v) do
            --    chatf("\t%s = %s", kk, vv)
            --end

            --local old_on_death= v.on_death
            --v.on_death= function(self, killer)
            --    chatf("on_death %s, %s -- old_on_death = %s", self, killer, old_on_death)
            --    return old_on_death(self, killer)
            --end
            
            local old_on_rightclick= v.on_rightclick
            v.on_rightclick= function(self, clicker)
                --chatf("on_rightclick %s, %s -- old_on_rightclick = %s", self, clicker, old_on_rightclick)
                return old_on_rightclick(self, clicker)
            end
            
            local old_on_punch= v.on_punch
            v.on_punch= function(self, puncher, time_from_last_punch, tool_capabilities, dir, damage)
                --chatf("on_punch %s health before = %.2f", self.name, guess_health(self))
                local ret= old_on_punch(self, puncher, time_from_last_punch, tool_capabilities, dir, damage)
                --chatf("on_punch %s health after = %.2f", self.name, guess_health(self))
                return ret
            end
            
            --local old_get_staticdata= v.get_staticdata
            --v.get_staticdata= function(self)
            --    chatf("get_staticdata %s", self.name)
            --    return old_get_staticdata(self, puncher, time_from_last_punch, tool_capabilities, dir, damage)
            --end
            
            minetest.register_entity(":" .. k, v)
        end
    end
end)
